#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sched.h>
#include <sys/file.h>

#include "tern/Replayer.h"
#include "tern/TernUtil.h"

using namespace llvm;
using namespace klee;
using namespace std;

// Defined in TernLandmark.h
extern const char* landmarks[TERN_LANDMARKS_NUM];
extern bool isIgnoredLandmarkInReplay(unsigned index);

Replayer *rp;
SchedCache *sCache;
CacheItr *cItr;
long symbolicTime = 0;

// thread-local storage.
__thread int localReplayTid = -1;


int ternCondWait(pthread_cond_t *cond, pthread_mutex_t *mutex) {
	//pthread_mutex_lock(mutex);
	//fprintf(stderr, "TID %d ternCondWait before\n", gettid());fflush(stderr);
	int result = pthread_cond_wait(cond, mutex);
	//fprintf(stderr, "TID %d ternCondWait after\n", gettid());fflush(stderr);
	//pthread_mutex_unlock(mutex);
	return result;
}

int ternCondSignal(pthread_cond_t *cond) {
	int result = pthread_cond_signal(cond);
	return result;
}

int ternCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *ts) {
	/*struct timeval ss;
	struct timeval ee;*/
	
	//pthread_mutex_lock(mutex);
	/*gettimeofday(&ss, NULL);
	fprintf(stderr, "TID %d ternCondTimedWait before\n", gettid());fflush(stderr);*/
	int result = pthread_cond_timedwait(cond, mutex, ts);
	/*gettimeofday(&ee, NULL);
	fprintf(stderr, "TID %d ternCondTimedWait after %ld us\n", 
		gettid(), (long)((ee.tv_sec - ss.tv_sec)*1000000 + (ee.tv_usec - ss.tv_usec)));fflush(stderr);*/
	//pthread_mutex_unlock(mutex);
	return result;
}

// Jingyue
void Replayer::setProgramArguments(int argc, char **argv) {
	// fprintf(stderr, "[Jingyue] setProgramArguments: %d %p\n", argc, (void *)argv);
	this->argc = argc;
	this->argv = argv;
}

// Jingyue
void Replayer::appendInputBuffer(char *input) {
	// fprintf(stderr, "[Jingyue] appendInputBuffer\n");
	lock();
	this->inputBuffer.push_back(input);
	unLock();
}

static const int MAX_LEN_PROG_NAME = 1024;
static const int MAX_N_ARGS = 1024;
static const char *REPLAY_SUFFIX = "-tern";
static const char *RECORD_SUFFIX = "-replay.bc";

// Jingyue
void Replayer::runRecordOnFailure() {
	// fprintf(stderr, "[Jingyue] runRecordOnFailure\n");
	if (IS_BATCH_PROGRAM) {
		fprintf(stderr, "%d %p\n", argc, (void *)argv);

		// Check arguments
		assert(argv && "<rp->argv> is not set. <tern_init> should have set it using <rp->setProgramArguments>");
		fprintf(stderr, "Program for replay: %s\n", argv[0]);
		size_t len = strlen(argv[0]);
		if (len < strlen(REPLAY_SUFFIX) || strcmp(argv[0] + (len - strlen(REPLAY_SUFFIX)), REPLAY_SUFFIX) != 0) {
			fprintf(stderr, "The program name must end with %s.\n", REPLAY_SUFFIX);
			assert(false);
		}

		// The .bc file name for recording
		char record_prog_name[MAX_LEN_PROG_NAME] = {0};
		strncpy(record_prog_name, argv[0], MAX_LEN_PROG_NAME);
		record_prog_name[len - strlen(REPLAY_SUFFIX)] = '\0'; // Remove the suffix
		strncat(record_prog_name, RECORD_SUFFIX, MAX_LEN_PROG_NAME);

		// Set up the argument list for recording
		char *new_argv[MAX_N_ARGS];
		new_argv[0] = (char *)"klee";
		new_argv[1] = record_prog_name;
		for (int i = 1; i < argc; ++i)
			new_argv[i + 1] = argv[i];
		new_argv[argc + 1] = NULL;

		// Run
		int ret = execvp("klee", new_argv);
		if (ret < 0) {
			perror("execvp");
			exit(-1);
		}
	} else {
		// Jingyue: if broken, send the inputs to the recorder.
		// Buffer will be cleared in taskEnd.
		lock();
#if 0
		cerr << "***** Begin message ******" << endl;
		for (size_t i = 0, E = inputBuffer.size(); i < E; ++i) {
			cerr << inputBuffer[i] << endl;
		}
		cerr << "***** End message *****" << endl;
#endif
		// cerr << "***** Begin send *****" << endl;
		forwardToRecorder();
		// cerr << "***** End send *****" << endl;
		inputBuffer.clear();
		numFailures++;
		unLock();
		// Racy is fine. Does not have to be very accurate. 
	}
}

// Jingyue: forward the input buffer to the recorder via network
int Replayer::forwardToRecorder() {
	// fprintf(stderr, "[Jingyue] forwardToRecorder\n");
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("[Warning] socket");
		return -1;
	}
	struct sockaddr_in svr_addr;
	bzero((char *)&svr_addr, sizeof(svr_addr));
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_addr.s_addr = inet_addr(get_option(tern, recorder_ip));
	svr_addr.sin_port = htons(get_option(tern, recorder_port));
	if (connect(sock, (struct sockaddr *)&svr_addr, sizeof(svr_addr)) < 0) {
		perror("[Warning] connect");
		return -1;
	}
	for (size_t i = 0, E = inputBuffer.size(); i < E; i++) {
		string toSend = inputBuffer[i] + "\n";
		int n_sent = send(sock, toSend.c_str(), toSend.length(), 0);
		if (n_sent < 0) {
			close(sock);
			return -1;
		}
	}
	char buffer[1024];
	int n_rcvd;
	while ((n_rcvd = recv(sock, buffer, 1024, 0)) > 0) {
		fprintf(stderr, "Received %d bytes\n", n_rcvd);
	}
	close(sock);
	return 0;
}

Replayer::Replayer(SchedCache *sc, CacheItr *cItr) {
	schedCache = sc;
	itr = cItr;
	initGlobals();
	initWaitQueue();
	inBatchNum = 0;
	outBatchNum = 0;
	admitFlag = true;
	brokenFlag = false;
	updateFlag = false;
	trackFailTries = 0;
	numWaiting = 0;
	rpEnterTaskTidOrder.clear();
	replayTids.clear();
	rpTidToTmplTidMap.clear();
	rpTidToBatchIDMap.clear();
	batchIDList.clear();
	for (unsigned i = 0; i < MAX_THREAD_NUM; i++) {
		isInTask[i] = 0;
	}
	replayIds = 0;
	replayStat.taskNum = 0;
	replayStat.finishNum = 0;
	pthread_cond_init(&taskStartCond, NULL);
	pthread_spin_init(&sLock, 0);
	pthread_rwlock_init(&pRwLock, NULL);
	spin_rwlock_init(&sRwLock);
	// Jingyue
	numFailures = 0;
}

// The idea of update region is for safely update the iterator for the schedule broken case. When you are moving the cache itr forward, you must
// enter the update region.
// Once the brokenFlag is set true, the updateFlag is also set 2, so that while the iterator is being flushed, no one could enter the region.
// The broken thread must wait for the updateFlag to be 0, and then set the flag to be 2, and then flush iterator.
// Other threads must wait for the updateFlag to be not 2, and then enter the update region.
// The reason I do not use rwlock is setting flag to 1 or 2 is more performance efficient.
void Replayer::startUpdate(int broken) {
	// Temp.
	if (IS_BATCH_PROGRAM != 1) {
		if (broken)
			realSpinLock(0);
		else
			realSpinLockRead(0);
	}
}

void Replayer::endUpdate(int broken) {
	// Temp.
	if (IS_BATCH_PROGRAM != 1) {
		//updateFlag = false;
		if (broken)
			realSpinUnLock(0);
		else
			realSpinUnLockRead(0);
	}
}

void Replayer::initGlobals() {
/*	for (int i = 0; i < MAX_WINDOW_NUM; i++) {
		rStat.windowHittedTimes[i] = 0;
	}
	for (int i = 0; i < MAX_WINDOW_NUM; i++) {
		rStat.windowFinishedTimes[i] = 0.0;
	}
	for (int i = 0; i < MAX_WINDOW_NUM; i++) {
		rStat.windowBrokenTimes[i] = 0;
	}
	for (int i = 0; i < MAX_WINDOW_NUM; i++) {
		rStat.windowTimeoutTimes[i] = 0;
	}*/
}

void Replayer::initWaitQueue() {
	for (unsigned i = 0; i < MAX_THREAD_NUM; i++) {
		waitQueue[i] = new WaitingThread(i);
	}
	for (unsigned i = 0; i < MAX_THREAD_NUM; i++) {
		condVarForTracks[i] = (pthread_cond_t *)malloc(sizeof(pthread_cond_t));
		memset(condVarForTracks[i], 0, sizeof(pthread_cond_t));
	}
}

void Replayer::resetWaitQueue() {
	for (unsigned i = 0; i < MAX_THREAD_NUM; i++) {
		waitQueue[i]->reset();
	}
}

void Replayer::wakeUpAllInWaitQueue() {
	int replayTid = tid();
	for (int i = 0; i < MAX_THREAD_NUM; i++) {
		if (i == replayTid)
			continue;
		waitQueue[i]->signal(replayTid);
	}
}

void Replayer::printUsage() {
  my_print( "your_program <constraint file> <executable program> <arguments for executable program>\n");
}

void Replayer::printTids(int *order, unsigned length) {
	my_print( "Tids: %u: ", length);
	for (unsigned i = 0; i < length; i++) {
		my_print( "%d ", order[i]);
	}
	my_print( "\n");
}

void Replayer::waitForTurn(const char *landmarkName, unsigned arg, int hookId) {
	rp_dbg("Replayer::waitForTurn TID %d MYTID %d, landmark %s, curTrunk %p, size %u, brokenFlag %d, hookId %d\n", 
		gettid(), tid(), landmarks[arg], (void *)itr->getCurTrunk(), itr->getCurTrunk()->size(), brokenFlag, hookId);
	int replayTid = tid();
	volatile int templateTid = itr->getTidMap(replayTid);
	bool broken = false;

	if (!hasSchedCache())
		return;

	endUpdate();
	
	waitQueue[replayTid]->block();

	startUpdate();

	if (!threadInTask(replayTid, false, 1)) {			// If another thread has printed broken information, I do not need to do this.
		rp_dbg("Replayer::waitForTurn TID %d return\n", gettid());
		return;
	}
	
	// This second check can be racy and not harmful.
	if (!itr->myTurn(arg, templateTid, hookId)) {
		rp_dbg("Replayer::waitForTurn NOT MY TURN, tid %d, mytid %d, my template tid %d, nextTid %d(Info: %s), TidFile %s\n", 
			gettid(), replayTid, templateTid, itr->getCurTrunk()->tidAt(itr->getOffset()), 
			itr->getCurTrunk()->tidInfoAt(itr->getOffset()), itr->getCurTrunk()->getTidFileName().c_str());
		broken = true;
	}
	
	if (!broken) {
		// If it is tern_symbolic, do not moveOn here, since symbolic() function will moveOn after determing the next trunk.
		// If it is pthread_create, do not moveOn here, since threadCreateEnd() function will moveOn after determing the replay tid of child thread.
		if (arg != TERN_SYMBOLIC_LANDMARK_IDX && arg != PTHREAD_CREATE_LANDMARK_IDX) {		
			moveOnItr(replayTid, hookId, arg);
		}
	} else {
		markBroken(arg, hookId, 0);
	}

	rp_dbg("TID %d MYTID %d returns from Replayer::waitForTurn %s\n", gettid(), replayTid, landmarks[arg]);
}

void Replayer::moveOnItr(int replayTid, int hookId, unsigned arg, unsigned dbgNum, SchedTrunk *nextTrunk) {
	replayer_debug( "Replayer::moveOnItr arg %u, dbgNum %u, landmark %s, ENTER TID %d MYTID %d\n", arg, dbgNum, landmarks[arg], gettid(), replayTid);

	int nextTid = -1;
	MoveOnRet result = itr->moveOn(replayTid, hookId, arg, nextTid);

	if (arg == TERN_SYMBOLIC_LANDMARK_IDX) {
		itr->moveItrToNextTrunk(replayTid, nextTrunk);
		nextTid = itr->getReversedTidMap(nextTrunk->tidAt(0));
		if (nextTid < 0) {
			if (nextTrunk->opAt(0) == START_TASK_LANDMARK_IDX)
				result = TaskStart;
		}
		rp_dbg("Replayer::moveOnItr MYTID %d switch trunk, new next tid %d, nextTrunk %s\n", replayTid, nextTid, nextTrunk->getTidFileName().c_str());
	}

	/*fprintf(stderr, "Replayer::moveOnItr, itr offset %d, tid %d mytid %d passes token to next mytid %d\n", 
		itr->getOffset(), gettid(), replayTid, nextTid);*/
	if (nextTid >= 0) {
		waitQueue[nextTid]->signal(replayTid);
	}
	
	if (result == SchedFinish) {		//One execution has successfully finished, init cache itr to the root to serve new requests.
		replayStat.finishNum++;
		if (replayStat.finishNum%get_option(tern, replayer_print_finishtime_interval) == 0)
			fprintf(stderr, "Replayer::moveOnItr finishTime %ld\n", replayStat.finishNum);
		initItr();
	}
	if (result == TaskStart) {		// A startTask landmark is met in the schedule.
		/*fprintf(stderr, "Replayer::moveOnItr next tid is unknown since it is task_start landmark, MYTID %d TID %d\n", replayTid, gettid());
		fflush(stderr);*/
		
		// Protect the admitFlag flag.		
		lock();
		admitOneTask(1);
		unLock();
	}
	if (result == OpMisMatch) {
		markBroken(arg, -1, 1);
	}
}

void Replayer::giveUpTurn(const char *landmarkName, unsigned arg) {
	// TO DO. DO NOT NEED THIS IN MY CURRENT IMPLEMENTATION.
}

// The markBroken function has already been called within update region.
void Replayer::markBroken(unsigned arg, int hookId, int callSite) {
	//const char *trunkName;
	rp_dbg("Replayer::markBroken, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
		gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
	int replayTid = tid();

	endUpdate();
	startUpdate(1);
	rp_dbg("Replayer::markBroken1, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d, brokenFlag %d, callSite %d\n", 
		gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag, brokenFlag, callSite);
	// If this flag has already been marked broken by another thread, and I just return.
	if (brokenFlag) {
		endUpdate(1);
		startUpdate();
		rp_dbg("Replayer::markBroken RET1, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
			gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
		return;
	}
	
	rp_dbg("Replayer::markBroken GOING TO GRAB LOCK, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
		gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
	lock();
	brokenFlag = true;
	admitFlag = false;
	unLock();

	SchedTrunk *curTrunk = itr->getCurTrunk();
	int offset = itr->getOffset();

	if (PRINT_BROKEN_SCHED_INFO) {
		fprintf(stderr, HIGHLIGHT_STR_BEFORE);
		fprintf(stderr, "TID %d MYTID %d, TPLTID %d, BROKEN(%d) hookId %d, move %u, offset %u, trunk size %u, curLandmark %s, but tidInfo {{%s}}, trunk %s\n",//. Trunk file %s.\n", 
			gettid(), replayTid, itr->getTidMap(replayTid), callSite, hookId, itr->getMove(), offset, curTrunk->size(), 
			landmarks[arg], curTrunk->tidInfoAt(offset), curTrunk->getTidFileName().c_str());
		fprintf(stderr, HIGHLIGHT_STR_AFTER);
		fflush(stderr);
	}

	if (IS_MYSQLD) {
		// Adjust the sched cache priority of trunks to make finish rate better.
		if (rpTidToBatchIDMap[replayTid] != -1)
			schedCache->adjustPriority(curTrunk, rpTidToBatchIDMap[replayTid], callSite);
	}
	initItr();
	wakeUpAllInWaitQueue();
	endUpdate(1);
	startUpdate();
}

inline int Replayer::tid() {
	if (localReplayTid == -1) {
		if (replayTids.find((int)pthread_self()) != replayTids.end()) {
			localReplayTid = replayTids[(int)pthread_self()];			
			return localReplayTid;
		} else
			return -1;
	} else
		return localReplayTid;
}

// This function must be called within the update region.
bool Replayer::threadInTask(int replayTid, bool needLock, unsigned callSite) {
	
	if (IS_BATCH_PROGRAM) {
		return (isInTask[0] > 0);
	}

	if (replayTid == -1) {
		/*fprintf(stderr, "Replayer::threadInTask hot\n");
		fflush(stderr);*/
		return false;
	}
	if (isInTask[replayTid] == 0) {	// 0 means this is a broken schedule.
		return false;
	}
	return true;
}

void Replayer::symbolic(void *addr, unsigned nbytes, const char *name) {
	rp_dbg("Replayer::symbolic START replayer gettid() %d\n", gettid());
	struct timeval ts;
	struct timeval te;

	if (PRINT_FINISH_SCHED_INFO) {
		gettimeofday(&ts, NULL);
		gettimeofday(&te, NULL);
	}

	int replayTid = tid();

	startUpdate();
	if (!threadInTask(replayTid, false, 2)) {
		rp_dbg("Replayer::symbolic START replayer gettid() %d threadInTask RETURN\n", gettid());
		endUpdate();
		return;
	}

	SchedTrunk *nextTrunk = NULL;
	char *newName = new char[strlen(name) + 10];
	memset(newName, 0, strlen(name) + 10);
	sprintf(newName, "%s%d", name, rpEnterTaskOrder());
	if ((nextTrunk = schedCache->queryNextTrunk(itr, addr, nbytes, newName, replayTid)) == NULL) {
		if (PRINT_BROKEN_SCHED_INFO) {
			fprintf(stderr, HIGHLIGHT_STR_BEFORE);
			fprintf(stderr, "NO SCHEDULE\n");
			fprintf(stderr, HIGHLIGHT_STR_AFTER);
			fflush(stderr);
		}
		//delete newName;
		markBroken(TERN_SYMBOLIC_LANDMARK_IDX, -1, 2);
		// Jingyue
		if (REPLAYER_LOAD_CACHE && RECORD_ON_FAILURE)
			this->runRecordOnFailure();
	} else {
		if (PRINT_FINISH_SCHED_INFO)
			gettimeofday(&te, NULL);
		moveOnItr(replayTid, -1, TERN_SYMBOLIC_LANDMARK_IDX, 0, nextTrunk);
	}

	if (PRINT_FINISH_SCHED_INFO) {
		long addTime = (te.tv_sec - ts.tv_sec)*1000000 + (te.tv_usec - ts.tv_usec);
		symbolicTime += addTime;
		rp_dbg("Replayer::symbolic SLEEP replayerTid %d, gettid() %d RETURN, symbolic add %ld\n", replayTid, gettid(), addTime);
	}
 
	rp_dbg("Replayer::symbolic RETURN replayer gettid() %d\n", gettid());

	endUpdate();
}

void Replayer::appStart() {	
	localReplayTid = replayTids[(int)pthread_self()] = replayIds++;
	if (IS_BATCH_PROGRAM) {
		taskStart(0, 0, 0, NULL, 0, NULL);
	}
	//fprintf(stderr, "Finish Replayer::appStart %x %d\n", (int)pthread_self(), localReplayTid);
}

void Replayer::appEnd(int *info) {
	//fprintf(stderr, "Replayer::appEnd start, symbolicTime %ld\n", symbolicTime);
	if (IS_BATCH_PROGRAM) {
		taskEnd(0, 0, 0);
	} 
	//fprintf(stderr, "Replayer::appEnd, symbolicTime %ld\n", symbolicTime);
	// FREE HEAP RESOURCE.
	// We do not need to call storeCache here, since the replayer just loads and uses the sched cache and does not writes into it.
}

// Always start the update region, and then acquire the lock(); Since the update region is only a loose region for the schedule broken case.
void Replayer::taskStart(int *winNum, int instId, int hookId, void *addr, unsigned nBytes, const char *symVarName) {
	rp_dbg("Replayer::taskStart coming task tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
		gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
	if (!hasSchedCache())
		return;
	
	lock();
	if (REPLAYER_USE_SCHED_TRACK)
		matchTrack(addr, nBytes, symVarName);			// This function will setup tidmap if any window is full.

	waitForAdmitTask();
	int replayTid = tid();

	// Update stat.
	replayStat.taskNum++;
	if (replayStat.taskNum%10000 == 0)
		fprintf(stderr, "Replayer::taskStart taskNum %ld\n", replayStat.taskNum);
	unLock();

	startUpdate();
	if (REPLAYER_USE_SCHED_TRACK) {
		if (IS_MYSQLD && rpTidToTmplTidMap[replayTid] == -1)
			markBroken(START_TASK_LANDMARK_IDX, -1, 4);
		else if (inBatchNum == 0) {
			itr->initSchedTrack(rpTidToBatchIDMap[replayTid], itr->getCurTrunk());
		}
	}
	lock();
	
	// inBatchNum must be updated, no matter whether the schedule is broken.
	if (inBatchNum == 0) {
		resetWaitQueue();
	}
	inBatchNum++;
	assert(inBatchNum <= TASK_WINDOW_SIZE);
	assert(inBatchNum >= outBatchNum);
	assert(inBatchNum > 0);

	// Must acquire the update region, and the mutex, and then check the brokenFlag.
	// From matchTrack(), if the tidmap is -1, then it is also a broken schedule.
	if (brokenFlag) {
		unLock();
		endUpdate();
		return;
	}

	// These flags are updated only when schedule is not broken.
	if (itr->getCurTrunk()->opAt(itr->getOffset()) != START_TASK_LANDMARK_IDX) {
		fprintf(stderr, "Replayer::taskStart coming task START LANDMARK MISMATCH tid %d, self %x, inBatchNum %u endoutBatchNum %u, \
			admitFlag %d, brokenFlag %d, offset %d, trunk %s\n", 
			gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag, brokenFlag, itr->getOffset(), itr->getCurTrunk()->getTidFileName().c_str());
		assert(false);
	}
	if (REPLAYER_USE_SCHED_TRACK) {
		if (!IS_MYSQLD)
			itr->setTidMap(replayTid, itr->getCurTrunk()->tidAt(itr->getOffset()));
		else {
			itr->setTidMap(replayTid, rpTidToTmplTidMap[replayTid]);
			rpTidToTmplTidMap.erase(replayTid);
		}
	} else {
		itr->setTidMap(replayTid, itr->getCurTrunk()->tidAt(itr->getOffset()));
	}
	assert(isInTask[replayTid] == 0);
	isInTask[replayTid] = time(NULL);
	assert(isInTask[replayTid] > 0);
	waitQueue[replayTid]->signal(replayTid);		// Assign the first init token to the first replay tid in each task.
	if (REPLAYER_USE_SCHED_TRACK)
		rpEnterTaskTidOrder[replayTid] = itr->getTidMap(replayTid);
	else
		rpEnterTaskTidOrder[replayTid] = inBatchNum;

	rp_dbg("Replayer::taskStart admitted task tid %d, self %x, mytid %d, inBatchNum %u endoutBatchNum %u, itr offset %u, temptid %d (%d), info %s, trunk %s\n", 
		gettid(), (int)pthread_self(), replayTid, inBatchNum, outBatchNum, itr->getOffset(), itr->getCurTrunk()->tidAt(itr->getOffset()), itr->getTidMap(replayTid),
		itr->getCurTrunk()->tidInfoAt(itr->getOffset()), itr->getCurTrunk()->getTidFileName().c_str());
	unLock();

	if (threadInTask(replayTid, false, 0))
		waitForTurn(__FUNCTION__, (unsigned)START_TASK_LANDMARK_IDX);
	endUpdate();
}

int Replayer::taskEnd(int *winNum, int instId, int hookId) {
	if (!hasSchedCache()) {
		// Cache is totally empty
		// Jingyue: forward inputs to the record
		if (REPLAYER_LOAD_CACHE && RECORD_ON_FAILURE) {
			this->runRecordOnFailure();
			startUpdate();
			reloadSchedCache();
			endUpdate();
		}
		return -1;
	}
	
	int replayTid = tid();
	startUpdate();
	if (threadInTask(replayTid, false, 0))
		waitForTurn(__FUNCTION__, (unsigned)END_TASK_LANDMARK_IDX);	
	endUpdate();

	startUpdate();
	lock();
	if (outBatchNum == 0)
		assert(inBatchNum >= 1);
	isInTask[replayTid] = 0;
	checkExceptionalTasks();

	outBatchNum++;
	assert(outBatchNum <= TASK_WINDOW_SIZE);
	rp_dbg("Replayer::taskEnd reset task tid %d mytid %d, inBatchNum %u endoutBatchNum %u, brokenFlag %d\n", 
		gettid(), replayTid, inBatchNum, outBatchNum, brokenFlag);
	if ((brokenFlag == false && outBatchNum == TASK_WINDOW_SIZE)
		|| (brokenFlag == true && outBatchNum == inBatchNum)) {
		rp_dbg("Replayer::taskEnd tid %d mytid %d, RESET BATCH inBatchNum %u outBatchNum %u, offset %d, move %u\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", 
			gettid(), replayTid, inBatchNum, outBatchNum, itr->getOffset(), itr->getMove());
		inBatchNum = 0;
		outBatchNum = 0;
		if (REPLAYER_USE_SCHED_TRACK) {
			/*for (unsigned i = 0; i < batchIDList.size(); i++) {
				fprintf(stderr, "MYTID %d RESET BATCH check track %u from track id list, mybatchid %d\n", replayTid, batchIDList.at(i), rpTidToBatchIDMap[replayTid]);
			}*/
			if (batchIDList.size() > 0 && rpTidToBatchIDMap[replayTid] == (int)batchIDList.at(0)) {
				rp_dbg("MYTID %d erase %u track from track id list\n", replayTid, batchIDList.at(0));
				batchIDList.erase(batchIDList.begin());
				rpTidToBatchIDMap.erase(replayTid);
			}
		}
		rpEnterTaskTidOrder.clear();

		// Jingyue: Clear the input buffer
		inputBuffer.clear();

		// Once a batch has finished, no matter whether this batch has finished successfully or broken, reset the broken flag.
		brokenFlag = false;
		admitOneTask(2);
	}
	if (REPLAYER_USE_SCHED_TRACK)
		rpTidToBatchIDMap.erase(replayTid);
	unLock();
	endUpdate();
	rp_dbg("Replayer::taskEnd LEAVE task tid %d mytid %d, inBatchNum %u endoutBatchNum %u, brokenFlag %d\n", 
		gettid(), replayTid, inBatchNum, outBatchNum, brokenFlag);
	return 0;
}

void Replayer::checkExceptionalTasks() {
/*	time_t now = time(NULL);
	Int_To_Time_Map::iterator itr = isInTask.begin();
	while (itr != isInTask.end()) {
		if (itr->second > 0 && now - itr->second > EXCEPTIONAL_TASK_INTERVAL) {
			replayer_debug( "Replayer::checkExceptionalTasks() %d %d.\n", (int)now, (int)itr->second);
			fflush(stderr);
			assert(false);
		}
		itr++;
	}*/
}

void Replayer::threadCreateStart(unsigned arg) {
	// NO OP.
}

// This function has already been within the update region.
void Replayer::threadCreateEnd(unsigned arg, int pthreadId) {
	replayTids[pthreadId] = replayIds++;

	int replayTid = replayTids[pthread_self()];
	int replayChildTid = replayTids[pthreadId];
	rp_dbg("TID %d MYTID %d Replayer::threadCreateEnd after thread %x SELF %x is created\n", gettid(), replayTid, pthreadId, (int)pthread_self());

	// If parent thread/process is in task, then child thread should also be in task.
	if (threadInTask(replayTid, false, 3)) {
		const char *tidInfo = itr->getCurTrunk()->tidInfoAt(itr->getOffset());
		char *childTid = strstr(tidInfo, "[");
		assert(childTid);
		childTid++;
		itr->setTidMap(replayChildTid, atoi(childTid));
		// There is a difficult problem here: we do not need when to remove the tidMap for this spawned thread,
		// since it is hard to capture thread exit event in replayer part (pthread_exit?, or just returns from thread sub routine?).
	}

	if (threadInTask(replayTid, false, 3))
		moveOnItr(replayTid, -1, arg);
	rp_dbg("TID %d MYTID %d(child mytid %d) Replayer::threadCreateEnd after thread pthread self %x is created RETURN\n", 
		gettid(), replayTid, replayChildTid, pthreadId);
}

void Replayer::beforeLandmark(unsigned arg0, unsigned hook_id, int instId, uint64_t arg1, uint64_t arg2, uint64_t arg3/*, std::string callStackStr*/) {
	int replayTid = tid();

	rp_dbg("TID %d MYTID %d (localCopy %d) TEMPTID %d Replayer::beforeLandmark %u, op %s, hookId %d\n", 
		gettid(), replayTid, localReplayTid, itr->getTidMap(replayTid), arg0, landmarks[arg0], hook_id);

	startUpdate();
	// If it is tern_symbolic, do not need to wait for turn, since it is always the first op in each trunk,
	// and at this time, the next trunk has not been decided yet (it will be decided in symbolic() function).
	if (threadInTask(replayTid, false, 4)) {
		waitForTurn(__FUNCTION__, arg0, hook_id);

		// Make pthread_cond_timedwait get rid of physical time.
		if (IS_BATCH_PROGRAM && arg0 == PTHREAD_COND_TIMEDWAIT_IDX) {
			struct timeval *te = (struct timeval *)arg3;
			if (te->tv_sec != 0) {
				te->tv_sec = 0;
				te->tv_usec = 0;
			}
		}
	}

	endUpdate();

	// Important: we must grab the lock before actually calling pthread_create, and release the lock after we have updated replayerTids.
	// No matter the order is broken or not, so this function should not be called within waitForTurn.
	if (arg0 == PTHREAD_CREATE_LANDMARK_IDX) {
		pthread_rwlock_wrlock(&pRwLock);
	}

	/*rp_dbg("RETURN MYTID %d TEMPTID %d Replayer::beforeLandmark %u, op %s, hookId %d\n", 
		replayTid, itr->getTidMap(replayTid), arg0, landmarks[arg0], hook_id);*/
}

void Replayer::afterLandmark(pthread_t *tid2, unsigned arg0, unsigned hook_id, int instId, uint64_t ret_val, uint64_t arg1, uint64_t arg2, uint64_t arg3/*, std::string callStackStr*/) {	
	if (arg0 == PTHREAD_CREATE_LANDMARK_IDX) {
		startUpdate();
		// This should be working even threads are not in task.
		threadCreateEnd(arg0, (int)(*tid2));
		endUpdate();
		pthread_rwlock_unlock(&pRwLock);

	} else if (arg0 == PTHREAD_COND_WAIT_IDX || arg0 == PTHREAD_COND_TIMEDWAIT_IDX) {
		//fprintf(stderr, "Replayer::afterLandmark MYTID %d %u %s, wait for timedwait condwait after\n", tid(), arg0, landmarks[arg0]);
		// Make sure acquiring arg2 and acquiring replayer mutex are not nested in order to avoid deadlock.
		pthread_mutex_unlock((pthread_mutex_t *)arg2);
		startUpdate();
		// For special processing at updating thread tids maintained by replayer.
		if (!threadInTask(tid(), false, 0)) {
			endUpdate();
			pthread_mutex_lock((pthread_mutex_t *)arg2);
			return;
		}
		waitForTurn(__FUNCTION__, arg0);
		endUpdate();
		pthread_mutex_lock((pthread_mutex_t *)arg2);
	}
}

void Replayer::disableAllTasks() {
	memset(isInTask, 0, MAX_THREAD_NUM*sizeof(int));
}

void batch_exit(void);

void Replayer::reloadSchedCache() {
	// Reload schedule cache
	// fprintf(stderr, "[Jingyue] Load schedule cache\n");
	int fl = open("/tmp/tern.lock", O_CREAT | O_RDONLY, 0777);
	flock(fl, LOCK_EX);
	sCache->loadCache();
	flock(fl, LOCK_UN);
	close(fl);
	fprintf(stderr, "Finished loading schedule cache\n");
	if (sCache->getRoot()) {
		assert(sCache->getRoot()->size() == 1);
		this->initItr();
		atexit(batch_exit);
	}
}

void Replayer::initItr() {
	itr->init();
	if (hasSchedCache()) {
		itr->setCurNode(sCache->getRoot());
		itr->setCurTrunk(sCache->getRoot()->getChild(0));
	}
	disableAllTasks();
	if (REPLAYER_LOAD_CACHE && RECORD_ON_FAILURE) {
		const static int MAX_NUM_FAILURES = 1;
		if (numFailures >= MAX_NUM_FAILURES) {
			if (!hasSchedCache())
				reloadSchedCache();
			numFailures = 0;
		}
	}
}

// For the thread that does not match any track, and it has raised the admitFlag before cond wait, so we have to wait, and then check.
/*void Replayer::waitForAdmitTaskAndCheck() {
	do {
		numWaiting++;
		ternCondWait(&taskStartCond, getMutex());
		numWaiting--;
	} while (admitFlag == false);
	assert(admitFlag);
	admitFlag = false;
	rp_dbg("Tid %d MyTid %d set admitFlag to 0\n", gettid(), tid());
}*/

void Replayer::waitForAdmitTask() {
	int replayTid = tid();
	rp_dbg("Tid %d MyTid %d Replayer::waitForAdmitTask %u %u\n", gettid(), tid(), batchIDList.size(), inBatchNum);
	
	
	while (admitFlag == false || 
		// This branch is to guarantee that the tids in a batch enter the task according to the mapped template tid order, 
		// and tasks in the same batch should enter tasks within the same batch.
		(REPLAYER_USE_SCHED_TRACK 
			&& (batchIDList.size() > 0 && ((int)batchIDList.at(0) != rpTidToBatchIDMap[replayTid] || (int)(inBatchNum+1) != rpTidToTmplTidMap[replayTid])))) {
		numWaiting++;
		ternCondWait(&taskStartCond, getMutex());
		numWaiting--;
	}
	assert(admitFlag);
	admitFlag = false;
	rp_dbg("Tid %d MyTid %d set admitFlag to 0\n", gettid(), tid());
}

void Replayer::admitOneTask(int callSite) {
	admitFlag = true;
	rp_dbg("Tid %d MyTid %d set admitFlag to 1, callSite %d\n", gettid(), tid(), callSite);
	pthread_cond_broadcast(&taskStartCond);
}

void Replayer::spinLock() {
}

void Replayer::spinUnLock() {
}

void Replayer::realSpinLockRead(int callSite) {
	spin_read_lock(&sRwLock);
}

void Replayer::realSpinUnLockRead(int callSite) {
	spin_read_unlock(&sRwLock);
}

void Replayer::realSpinLock(int callSite) {
	spin_write_lock(&sRwLock);
}

void Replayer::realSpinUnLock(int callSite) {
	spin_write_unlock(&sRwLock);
}

bool Replayer::hasSchedCache() {
	return (schedCache->getRoot() != NULL);
}

void Replayer::waitUntilThreadCreateEnd() {
	if (localReplayTid == -1) {
		pthread_rwlock_rdlock(&pRwLock);
		pthread_rwlock_unlock(&pRwLock);
		localReplayTid = tid();
		rp_dbg("TID %d MYTID %d Replayer::waitUntilThreadCreateEnd after thread pthread self %x is created RETURN\n", 
			gettid(), localReplayTid, (int)pthread_self());
	}
}

int Replayer::rpEnterTaskOrder() {
	if (IS_BATCH_PROGRAM)
		return 0;
	else
		return rpEnterTaskTidOrder[tid()];
}

// This function is protected by the replayer mutex within the matchTrack() function, so updating the map flags and batchSerial is safe.
int Replayer::checkTrackAt(SchedTrack *track, unsigned trackId, std::vector< std::vector<unsigned char> > &values) {
	unsigned index = track->getIndex();
	int replayTid = tid();
	
	if (track->checkTrackAt(trackId,  values, replayTid)) {
		rp_dbg("Replayer::matchTrack2 match track index %u at slot %u(%s), tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d, emptyNum %u\n", 
			index, trackId, track->getTrackId(), gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag, track->emptyNum());
		track->insertReplayTidAt(trackId, replayTid);
		if (track->isFull()) {
			int wakeUpTid;
			fprintf(stderr, "Track %u is FULLL(%s)\n", index, track->getTrackId());
			for (unsigned j = 0; j < TASK_WINDOW_SIZE; j++) {
				wakeUpTid = track->start(j);
				schedCache->delRPTidFromAllTracks(wakeUpTid);
				rpTidToTmplTidMap[wakeUpTid] = j+1;									// Since all the tids in a schedule batch are all normalized from 1 to ..., so this is correct.
				rpTidToBatchIDMap[wakeUpTid] = index;
				rp_dbg("Track %u is FULL2: TID %d MYTID %d is mapped to template tid %d\n", index, gettid(), wakeUpTid, j+1);
				if (j != trackId) {
					rp_dbg("MYTID %d signals mytid %d at cond var %p\n", replayTid, wakeUpTid, (void *)condVarForTracks[wakeUpTid]);
					ternCondSignal(condVarForTracks[wakeUpTid]);
				}
			}
			batchIDList.push_back(index);
			rp_dbg("MYTID %d push %u track to track id list\n", replayTid, index);
			return 1;
		} else {
			rp_dbg("MYTID %d returns 2\n", replayTid);
			return 2;																	// Only find at most one matching slot in a track. Ask the caller to break the loop;
		}
	}

	return 0;
}

void Replayer::prepareForBatch() {
	
}

bool Replayer::matchTrack(void *addr, unsigned nBytes, const char *symVarName) {
	if (!IS_MYSQLD)
		return true;

	int replayTid = tid();
	int matchCnt = 0;
	int limit = 10;
	
	// Fill values.
	//rp_dbg("Replayer::hasEnabledTasks ENTER, addr %p, nBytes %u, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
		//(void *)addr, nBytes, gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
	std::vector< std::vector<unsigned char> > values = std::vector< std::vector<unsigned char> >(1);	
	std::vector<unsigned char> &data = values[0];
	data.clear();
	for (unsigned k = 0; k < nBytes; k++) {
		data.push_back((char)(*((long *)((long)addr + k))));
	}


	//rp_dbg("Replayer::hasEnabledTasks ENTER2, curTrunk %p, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
	//(void *)curTrunk, gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
	SchedTrunk *curTrunk = schedCache->getRoot()->getChild(0);
	std::vector<unsigned>::iterator trackItr = curTrunk->getEnabledTracks().begin();
	// For all enabled tracks.
	while (trackItr != curTrunk->getEnabledTracks().end()) {
		unsigned index = *trackItr;
		//rp_dbg("Tid %d MYTID %d checking index %u track, trackFailTries %u\n", gettid(), replayTid, index, trackFailTries);
		SchedTrack *track = schedCache->getTrackByIndex(index);
		std::set<unsigned> checkedSlots;
		int checkResult = 0;

		// First, try to find an empty slot, if the track is not full. This could help to fullfil the track.
		if (!track->isFull()) {
			for (unsigned i = 0; i < TASK_WINDOW_SIZE; i++) {
				if (!track->isEmptyAt(i))
					continue;
				checkedSlots.insert(i);
				checkResult = checkTrackAt(track, i, values);
				//if (checkResult > 0) {
					if (++matchCnt > limit)
						break;
				//}
				
				if (checkResult == 2) {
					rp_dbg("MYTID %d push break1\n", replayTid);
					break;							// Hit a match but track is not full yet.
				} else if (checkResult == 1) {
					rp_dbg("MYTID %d push return true1\n", replayTid);
					return true;					// Hit and track is full
				}
			}
		}
		//rp_dbg("Tid %d MYTID %d checking index %u track, middle %u\n", gettid(), replayTid, index, trackFailTries);
		if (++matchCnt >= limit) {
			break;
		}
		if (checkResult == 0) {
			// If empty slot does not match, then restart and try to match non-empty slots.
			for (unsigned i = 0; i < TASK_WINDOW_SIZE; i++) {
				if (checkedSlots.find(i) != checkedSlots.end())
					continue;
				checkResult = checkTrackAt(track, i, values);
				//if (checkResult > 0) {
					if (++matchCnt > limit)
						break;
				//}
					
				if (checkResult == 2) {
					rp_dbg("MYTID %d push break2\n", replayTid);
					break;							// Hit a match but track is not full yet.
				} else if (checkResult == 1) {
					rp_dbg("MYTID %d push return true2\n", replayTid);
					return true;					// Hit and track is full
				}
			}
		}
		if (++matchCnt >= limit) {
			break;
		}
		trackItr++;
	}

	//rp_dbg("Tid %d MYTID %d checking index %u track, middle %u, match %d\n", gettid(), replayTid, index, trackFailTries, matchCnt);
	// If no match, then directly return, do not wait.
	if (matchCnt == 0) {
		rp_dbg("Replayer::matchTrack NOMATCH miss all tracks, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
			gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
		rpTidToTmplTidMap[replayTid] = -1;
		rpTidToBatchIDMap[replayTid] = -1;
		return false;
	}
	
	// If reach here, means no track is full yet.
	struct timespec ts;
	struct timeval tv;
	gettimeofday(&tv, NULL);
	//ts.tv_sec = tv.tv_sec + TRACK_WAIT_INTERVAL;
	//ts.tv_nsec = tv.tv_usec*1000;

	// Set timeout to be 0.2 second.
	if (tv.tv_usec > 950000) {
		ts.tv_sec = tv.tv_sec + 1;
		ts.tv_nsec = 0;
	} else {
		ts.tv_sec = tv.tv_sec;
		ts.tv_nsec = (tv.tv_usec + 40000)*1000;
	}
	
	rpTidToTmplTidMap[replayTid] = -1;												// Assume this thread dos not have schedule and will timeout and wake up.
	rp_dbg("MYTID %d wait at cond var %p\n", replayTid, (void *)condVarForTracks[replayTid]);
	ternCondTimedWait(condVarForTracks[replayTid], getMutex(), &ts);
	rp_dbg("MYTID %d wake up at cond var %p\n", replayTid, (void *)condVarForTracks[replayTid]);
	schedCache->delRPTidFromAllTracks(replayTid);
	// If a thread is waken up from here without any tid map, then it does not have tid map, then once it enters the taskStart, we know this is broken schedule.
	if (rpTidToTmplTidMap[replayTid] == -1) {
		rp_dbg("Replayer::matchTrack DONE4 markBroken, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
			gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
		rpTidToBatchIDMap[replayTid] = -1;
		return false;
	}
	rp_dbg("Replayer::matchTrack DONE RETURN TRUE, tid %d, self %x, inBatchNum %u endoutBatchNum %u, admitFlag %d\n", 
		gettid(), (int)pthread_self(), inBatchNum, outBatchNum, admitFlag);
	return true;
}

void Replayer::calStatStart() {

}

void Replayer::calStatEnd() {

}

void batch_exit(void) {
#ifdef REPLAYER_ORDER
	assert(rp);
	rp->appEnd(NULL);
#endif
}

// Jingyue: used to capture input in HTTP
// TODO: more general so as to capture inputs for other applications
extern "C" void tern_capture_input(char *input, int ret, int nRead) {
	if (REPLAYER_LOAD_CACHE && RECORD_ON_FAILURE) {
		// fprintf(stderr, "[Jingyue] tern_capture_input %p, %d, %d\n", (void *)input, ret, nRead);
		if (ret != 0)
			return;
		// Copy input to replayer's input buffer
		// once the replay failed, we send the input to the recorder
		rp->appendInputBuffer(input);
	}
}

// This function is hooked into the beginning of main() function of each program which is going to be replayed by Ece.
extern "C" void tern_init(int argc, char **argv) {
#ifdef REPLAYER_ORDER
	// YJF: load and print options for replay
	load_options("local.options");
	//print_options();

	sCache = new SchedCache();
	cItr = new CacheItr();
	rp = new Replayer(sCache, cItr);
	// Jingyue: store the program arguments
	rp->setProgramArguments(argc, argv);

	if (IS_BATCH_PROGRAM)
		assert(TASK_WINDOW_SIZE == 1);
	
	if (REPLAYER_LOAD_CACHE)
		sCache->loadCache();					// After this is implemented, we do not need the code below.
	
	if (sCache->getRoot()) {
		assert(sCache->getRoot()->size() == 1);
		rp->initItr();
		atexit(batch_exit);
	} else {
		if (PRINT_BROKEN_SCHED_INFO) {
			fprintf(stderr, HIGHLIGHT_STR_BEFORE);
			fprintf(stderr, "NO SCHEDULE CACHE\n");
			fprintf(stderr, HIGHLIGHT_STR_AFTER);
		}
		rp->disableAllTasks();
		if (get_option(tern, record_on_failure) && IS_BATCH_PROGRAM)
			rp->runRecordOnFailure();
	}
	rp->appStart();
#endif
}

// Heming: the new integrated symbolic interface for both recording and replaying part.
extern "C" void tern_symbolic(void *addr, unsigned nbytes, const char *name) {
#ifdef REPLAYER_ORDER
	rp->symbolic(addr, nbytes, name);
#endif
}

// This hook function will be hooked before each landmark.
extern "C" void tern_before_landmark(unsigned arg0, unsigned hook_id, uint64_t arg1, uint64_t arg2, uint64_t arg3) {	
#ifdef REPLAYER_ORDER
	/*fprintf(stderr, "tern_before_landmark PREV tid %d, %s. arg1 %lx, arg2 %lx, arg3 %lx.\n", 
		gettid(), landmarks[arg0], (unsigned long)arg1, (unsigned long)arg2, (unsigned long)arg3);
	fflush(stderr);*/

	if (isIgnoredLandmarkInReplay(arg0))
		return;

	rp->waitUntilThreadCreateEnd();

	/*fprintf(stderr, "tern_before_landmark mytid %d, tid %d, %s. arg1 %lx, arg2 %lx, arg3 %lx.\n", 
		-1, gettid(), landmarks[arg0], (unsigned long)arg1, (unsigned long)arg2, (unsigned long)arg3);
	fflush(stderr);*/
	// This is necessary, since before tern_symbolic, we can not determine which sched trunk to use.
	if (arg0 == START_TASK_LANDMARK_IDX || arg0 == END_TASK_LANDMARK_IDX)
		return;

	rp->beforeLandmark(arg0, hook_id, 0, arg1, arg2, arg3);
#endif
}

// This hook function will be hooked after each landmark.
extern "C" void tern_after_landmark(pthread_t *tid, unsigned arg0, unsigned hook_id, uint64_t ret_val, uint64_t arg1, uint64_t arg2, uint64_t arg3) {
#ifdef REPLAYER_ORDER
	/*fprintf(stderr, "tern_after_landmark mytid %d, tid %d, %s. arg1 %lx, arg2 %lx, arg3 %lx.\n", 
		rp->tid(), gettid(), landmarks[arg0], (unsigned long)arg1, (unsigned long)arg2, (unsigned long)arg3);
	fflush(stderr);*/

	rp->afterLandmark(tid, arg0, hook_id, 0, ret_val, arg1, arg2, arg3);

	/*fprintf(stderr, "tern_after_landmark RETURN mytid %d, tid %d, %s. arg1 %lx, arg2 %lx, arg3 %lx.\n", 
		rp->tid(), gettid(), landmarks[arg0],
		(unsigned long)arg1, (unsigned long)arg2, (unsigned long)arg3);
	fflush(stderr);*/
#endif
}

extern "C" void tern_task_start(void *addr, unsigned nBytes, const char *symVarName) {
#ifdef REPLAYER_ORDER
	rp->taskStart(0, 0, 0, addr, nBytes, symVarName);
#endif
}

extern "C" void tern_task_end() {
#ifdef REPLAYER_ORDER
	rp->taskEnd(0, 0, 0);
#endif
}

// For future use, consumer one order.
extern "C" void tern_order() {
#ifdef REPLAYER_ORDER
	// Empty.
	fprintf(stderr, "tern_order");
#endif
}

